Skip to content

fix(security): replace wildcard CORS with allowlist and harden session cookies#467

Open
Ridanshi wants to merge 2 commits into
GitMetricsLab:mainfrom
Ridanshi:fix/cors-allowlist-session-cookie-hardening
Open

fix(security): replace wildcard CORS with allowlist and harden session cookies#467
Ridanshi wants to merge 2 commits into
GitMetricsLab:mainfrom
Ridanshi:fix/cors-allowlist-session-cookie-hardening

Conversation

@Ridanshi
Copy link
Copy Markdown

@Ridanshi Ridanshi commented May 23, 2026

Closes

Closes #454


Problem

backend/server.js applied cors('*'), which:

  1. Sent Access-Control-Allow-Origin: * on every response — including authenticated endpoints — making it impossible to attach session cookies to cross-origin requests (browsers refuse credentials with a wildcard ACAO).
  2. Left the session cookie without SameSite, Secure, or explicit HttpOnly attributes — cookie transmitted in plaintext over HTTP and vulnerable to CSRF via cross-site navigations.
  3. Had no startup guard — a missing FRONTEND_ORIGIN in production would silently degrade to an open wildcard, with no operator-visible error.

Changes

backend/config/validateEnv.js (new)

Extracts startup environment validation into a standalone, unit-testable module. In production the server calls process.exit(1) with a clear error if FRONTEND_ORIGIN is unset; in development it defaults to http://localhost:5173 and logs a warning.

backend/server.js

  • Replaces cors('*') with a function-based origin allowlist driven by FRONTEND_ORIGIN.
    A string value in the cors origin option sends the header unconditionally; a function is required to suppress the header entirely for unlisted origins.
  • Adds credentials: true so the browser sends the session cookie on cross-origin requests.
  • Adds httpOnly: true, secure (production-only), and sameSite: 'strict' to the session cookie.

src/pages/Login/Login.tsx · src/pages/Signup/Signup.tsx

  • Adds withCredentials: true to both Axios calls so the browser attaches and stores the session cookie for cross-origin requests.

spec/auth.routes.spec.cjs

  • Restructures the existing tests under a single outer describe to avoid Mongoose connection conflicts with Jasmine's random seed ordering.
  • Resolves mongoose, passport, and express-session from the backend directory so the test app and the application code share one module instance (prevents operation-buffering timeouts when backend/node_modules exists).
  • Adds CORS behaviour suite: allowed origin reflected, disallowed origin absent, preflight, credentialed login.
  • Adds Session cookie security flags suite: HttpOnly and SameSite=Strict assertions.
  • Adds Environment validation suite: production throws without FRONTEND_ORIGIN, non-production does not.
  • Updates test passwords to satisfy the signupSchema Zod regex so signup and logout route tests exercise the full validation path.

spec/user.model.spec.cjs

  • Applies the same require.resolve fix so the User model tests share the backend's mongoose instance.

backend/.env.example · .env.example (new)

Documents all required environment variables, including FRONTEND_ORIGIN.

README.md

Adds an Environment Variables table to the setup guide.


New environment variable

Variable Required Dev default Description
FRONTEND_ORIGIN Yes in production http://localhost:5173 Exact URL of the frontend; restricts the CORS allowlist

Test results

20 specs, 0 failures   (Jasmine, randomised seed)

Behaviour preserved

  • Login, signup, and logout API contracts unchanged.
  • Session semantics and Passport authentication flow unchanged.
  • Development workflow requires no additional setup — FRONTEND_ORIGIN defaults automatically.

Test plan

  • Copy backend/.env.examplebackend/.env; set FRONTEND_ORIGIN=http://localhost:5173; confirm server starts.
  • Log in from the frontend; confirm the response sets a cookie with HttpOnly; SameSite=Strict.
  • Send a request with Origin: http://evil.example.com; confirm no Access-Control-Allow-Origin header in the response.
  • Remove FRONTEND_ORIGIN, set NODE_ENV=production; confirm the server refuses to start.
  • Run npm run test:backend — all 20 tests pass.

Summary by CodeRabbit

  • Documentation

    • Setup guide now includes environment variable configuration instructions with required variables documented.
  • Security

    • CORS now restricted to configured frontend origin instead of allowing all requests.
    • Session cookies hardened with HttpOnly and SameSite security flags.
    • Login and signup now properly support session-based authentication with credential transmission.
  • Tests

    • Added comprehensive authentication tests including CORS header and session security validation.
  • Chores

    • Added testing and HTTP request testing dependencies.

Review Change Stack

Ridanshi added 2 commits May 23, 2026 23:24
…n cookies

- Replace `cors('*')` with a function-based origin allowlist driven by the
  FRONTEND_ORIGIN environment variable, so ACAO headers are only set for the
  configured origin and are absent for all other origins.
- Add `credentials: true` to the CORS config so the browser can send the
  session cookie on cross-origin requests.
- Set `httpOnly: true`, `secure` (production-only), and `sameSite: 'strict'`
  on the session cookie to prevent XSS exfiltration, plaintext transmission,
  and CSRF via cross-site navigations.
- Add `withCredentials: true` to the Axios calls in Login and Signup so the
  browser includes the session cookie on cross-origin requests.
- Extract startup environment validation into `backend/config/validateEnv.js`
  so the server refuses to start in production when FRONTEND_ORIGIN is unset,
  while falling back to localhost:5173 in development with a logged warning.

Fixes GitMetricsLab#454
…alidation

- Rewrite auth.routes.spec.cjs with a shared outer describe for MongoDB-
  dependent tests to eliminate connection conflicts under Jasmine's random
  seed ordering.
- Resolve mongoose and passport from the backend directory in both spec files
  so tests and application code share one module instance, preventing
  operation-buffering timeouts when backend/node_modules is present.
- Add CORS behaviour suite: allowed origin, disallowed origin, preflight, and
  credentialed-login assertions.
- Add session cookie flags suite: HttpOnly and SameSite=Strict assertions.
- Add environment validation suite: production throws without FRONTEND_ORIGIN,
  development does not.
- Update test passwords to satisfy the signupSchema regex so signup and logout
  route tests exercise the full validation path.
- Add jasmine and supertest as devDependencies in backend/package.json.
- Add cors as a devDependency in root package.json for test app setup.
- Add .env.example and backend/.env.example documenting all required variables
  including FRONTEND_ORIGIN.
- Add Environment Variables table to README setup guide.
@netlify
Copy link
Copy Markdown

netlify Bot commented May 23, 2026

Deploy Preview for github-spy ready!

Name Link
🔨 Latest commit 4d5fc14
🔍 Latest deploy log https://app.netlify.com/projects/github-spy/deploys/6a120df7dd67d6000845ffee
😎 Deploy Preview https://deploy-preview-467--github-spy.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 23, 2026

📝 Walkthrough

Walkthrough

Backend CORS configuration transitions from permissive wildcard to explicit frontend origin allowlist, session cookies gain security flags (httpOnly, secure, sameSite), environment variables are validated at startup, frontend auth requests include credentials, and comprehensive test coverage validates CORS behavior, cookie security, and configuration enforcement.

Changes

CORS Allowlist & Session Cookie Security

Layer / File(s) Summary
Environment Configuration & Documentation
.env.example, README.md, backend/.env.example, package.json
Frontend backend URL and backend port/MongoDB/session/CORS origin environment variables documented in .env.example files; README includes new "Configure environment variables" section with setup commands and required variable descriptions; root package.json adds cors dependency.
Backend Environment Validation at Startup
backend/config/validateEnv.js, backend/server.js
validateEnv() module enforces FRONTEND_ORIGIN presence in production; server.js startup wraps validation in try/catch to exit immediately with logged fatal error on invalid configuration.
Backend CORS Allowlist & Session Cookie Hardening
backend/server.js, backend/package.json
CORS middleware replaces cors('*') with function-based origin check allowing only FRONTEND_ORIGIN (default http://localhost:5173), enables credentials, restricts methods/headers; session middleware adds explicit cookie settings (httpOnly: true, secure in production, sameSite: 'strict'); development dependencies add jasmine and supertest.
Frontend Auth Requests with Credentials
src/pages/Login/Login.tsx, src/pages/Signup/Signup.tsx
Login and Signup axios POST calls updated to include { withCredentials: true }, enabling browser to send session cookies with cross-origin requests.
Test Infrastructure & Comprehensive Security Coverage
spec/auth.routes.spec.cjs, spec/user.model.spec.cjs
Test app setup refactored to share backend module instances; auth.routes.spec expands with signup/login/logout integration tests, CORS origin allowlist validation, allowed/unlisted origin behavior, credentialed request headers, OPTIONS preflight responses, session cookie security flags (HttpOnly, SameSite=Strict), and environment validation unit tests for production FRONTEND_ORIGIN requirement; user.model.spec resolves mongoose from backend/node_modules.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • GitMetricsLab/github_tracker#450: Both PRs modify backend CORS configuration to replace permissive wildcard policy with environment-driven origin allowlist (this PR uses FRONTEND_ORIGIN, #450 uses CLIENT_URL) and enable credentialed requests for cross-origin authentication.

Suggested labels

level:intermediate, quality:clean

Poem

🐰 A rabbit hops through origins fair,
With CORS rules now declared with care,
No more wildcard's risky sprawl—
Just FRONTEND_ORIGIN, allowed in all,
Cookies secure, with flags held tight,
Cross-origin auth done right! 🔒✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main security-focused changes: replacing wildcard CORS with an allowlist and hardening session cookies.
Description check ✅ Passed The description follows the template structure, clearly explains the problem, details all changes across multiple files, includes test results, test plan, and behavior preservation notes.
Linked Issues check ✅ Passed The PR fully addresses all acceptance criteria from issue #454: wildcard CORS removed, allowlist configured, credentialed requests enabled, session cookies hardened with HttpOnly/SameSite/Secure flags, and environment validation added.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #454 requirements: environment validation, CORS/cookie hardening, frontend credential handling, tests restructuring, and documentation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 Thank you @Ridanshi for your contribution. Please make sure your PR follows https://github.com/GitMetricsLab/github_tracker/blob/main/CONTRIBUTING.md#-pull-request-guidelines

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
spec/auth.routes.spec.cjs (1)

74-85: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Restore FRONTEND_ORIGIN after this suite.

This suite mutates global process env in setup but does not restore it in teardown, which can leak state into other specs and cause order-dependent failures.

Suggested patch
 describe('Backend auth integration', () => {
   let app;
+  let hadFrontendOrigin;
+  let savedFrontendOrigin;

   beforeAll(async () => {
+    hadFrontendOrigin = Object.prototype.hasOwnProperty.call(process.env, 'FRONTEND_ORIGIN');
+    savedFrontendOrigin = process.env.FRONTEND_ORIGIN;
     process.env.FRONTEND_ORIGIN = ALLOWED_ORIGIN;
     await mongoose.connect('mongodb://127.0.0.1:27017/github_tracker_test');
     app = createTestApp();
   });

   afterAll(async () => {
+    if (hadFrontendOrigin) {
+      process.env.FRONTEND_ORIGIN = savedFrontendOrigin;
+    } else {
+      delete process.env.FRONTEND_ORIGIN;
+    }
     if (mongoose.connection.readyState === 1) {
       await mongoose.connection.db.dropDatabase();
       await mongoose.disconnect();
     }
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/auth.routes.spec.cjs` around lines 74 - 85, The test suite sets
process.env.FRONTEND_ORIGIN in beforeAll but never restores it; capture the
original value at start of beforeAll (e.g., const _origFrontendOrigin =
process.env.FRONTEND_ORIGIN) and in afterAll restore it
(process.env.FRONTEND_ORIGIN = _origFrontendOrigin or delete
process.env.FRONTEND_ORIGIN if originally undefined) so that the global
environment is returned to its prior state; update the beforeAll/afterAll blocks
(referencing beforeAll, afterAll, process.env.FRONTEND_ORIGIN, and
ALLOWED_ORIGIN) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/config/validateEnv.js`:
- Around line 6-13: The validateEnv function only enforces FRONTEND_ORIGIN in
production but must also ensure a strong session secret; update validateEnv() to
check process.env.SESSION_SECRET when NODE_ENV === 'production' and throw a
descriptive Error if it's missing or clearly insecure (e.g., empty, placeholder
like "changeme", or shorter than a minimum length such as 32 chars). Reference
the validateEnv function and add the SESSION_SECRET checks and error message so
production cannot start without a valid SESSION_SECRET.

In `@backend/server.js`:
- Around line 59-63: The cookie config sets secure: process.env.NODE_ENV ===
'production' but Express is not configured to trust a TLS-terminating proxy, so
secure cookies will not be sent; add app.set('trust proxy', true) (or a more
specific trust value) in backend/server.js during app initialization (before
middleware that sets cookies/session) so Express will honor the secure flag
behind proxies; update any related comments and ensure this runs only in
environments where a proxy is present (e.g., conditionally when NODE_ENV ===
'production' or when an env var like TRUST_PROXY is set).

---

Outside diff comments:
In `@spec/auth.routes.spec.cjs`:
- Around line 74-85: The test suite sets process.env.FRONTEND_ORIGIN in
beforeAll but never restores it; capture the original value at start of
beforeAll (e.g., const _origFrontendOrigin = process.env.FRONTEND_ORIGIN) and in
afterAll restore it (process.env.FRONTEND_ORIGIN = _origFrontendOrigin or delete
process.env.FRONTEND_ORIGIN if originally undefined) so that the global
environment is returned to its prior state; update the beforeAll/afterAll blocks
(referencing beforeAll, afterAll, process.env.FRONTEND_ORIGIN, and
ALLOWED_ORIGIN) accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6708409e-4e7e-4b6b-a7b5-4f6a243abf95

📥 Commits

Reviewing files that changed from the base of the PR and between 373dde2 and 4d5fc14.

📒 Files selected for processing (11)
  • .env.example
  • README.md
  • backend/.env.example
  • backend/config/validateEnv.js
  • backend/package.json
  • backend/server.js
  • package.json
  • spec/auth.routes.spec.cjs
  • spec/user.model.spec.cjs
  • src/pages/Login/Login.tsx
  • src/pages/Signup/Signup.tsx

Comment on lines +6 to +13
function validateEnv() {
if (process.env.NODE_ENV === 'production' && !process.env.FRONTEND_ORIGIN) {
throw new Error(
'FRONTEND_ORIGIN environment variable is required in production. ' +
'Set it to the URL of your frontend (e.g., https://app.example.com).'
);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Require SESSION_SECRET validation in production.

validateEnv() currently checks only FRONTEND_ORIGIN, so production can still start with an empty or placeholder session secret, which weakens session integrity.

🔧 Suggested patch
 function validateEnv() {
-  if (process.env.NODE_ENV === 'production' && !process.env.FRONTEND_ORIGIN) {
+  const isProduction = process.env.NODE_ENV === 'production';
+  const frontendOrigin = process.env.FRONTEND_ORIGIN?.trim();
+  const sessionSecret = process.env.SESSION_SECRET?.trim();
+
+  if (isProduction && !frontendOrigin) {
     throw new Error(
       'FRONTEND_ORIGIN environment variable is required in production. ' +
       'Set it to the URL of your frontend (e.g., https://app.example.com).'
     );
   }
+
+  if (isProduction && (!sessionSecret || sessionSecret === 'replace-with-a-long-random-string')) {
+    throw new Error(
+      'SESSION_SECRET must be set to a strong random value in production.'
+    );
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/config/validateEnv.js` around lines 6 - 13, The validateEnv function
only enforces FRONTEND_ORIGIN in production but must also ensure a strong
session secret; update validateEnv() to check process.env.SESSION_SECRET when
NODE_ENV === 'production' and throw a descriptive Error if it's missing or
clearly insecure (e.g., empty, placeholder like "changeme", or shorter than a
minimum length such as 32 chars). Reference the validateEnv function and add the
SESSION_SECRET checks and error message so production cannot start without a
valid SESSION_SECRET.

Comment thread backend/server.js
Comment on lines +59 to +63
cookie: {
httpOnly: true,
// Only transmit the cookie over HTTPS in production.
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether secure cookies are enabled without trust proxy in backend/server.js
rg -n -C2 "secure:\s*process\.env\.NODE_ENV === 'production'|app\.set\(\s*['\"]trust proxy['\"]" backend/server.js

Repository: GitMetricsLab/github_tracker

Length of output: 245


Configure Express trust proxy for production secure cookies

backend/server.js enables secure via secure: process.env.NODE_ENV === 'production', but it doesn’t set app.set('trust proxy', ...) in this file. Behind a TLS-terminating proxy, Express must trust the proxy for secure-cookie handling.

🔧 Suggested patch
 const app = express();
+
+if (process.env.NODE_ENV === 'production') {
+  app.set('trust proxy', 1);
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/server.js` around lines 59 - 63, The cookie config sets secure:
process.env.NODE_ENV === 'production' but Express is not configured to trust a
TLS-terminating proxy, so secure cookies will not be sent; add app.set('trust
proxy', true) (or a more specific trust value) in backend/server.js during app
initialization (before middleware that sets cookies/session) so Express will
honor the secure flag behind proxies; update any related comments and ensure
this runs only in environments where a proxy is present (e.g., conditionally
when NODE_ENV === 'production' or when an env var like TRUST_PROXY is set).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 Bug Report: Wildcard CORS Configuration Combined With Missing Cookie Security Flags Creates Unsafe Cross-Origin Session Behavior

1 participant